/*
Uninclude: Tool for detecting unused #included files in C and C++ code.
    Copyright (C) 2012  Daniel Gutson, FuDePAN

    Uninclude is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Uninclude is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Uninclude.  If not, see <http://www.gnu.org/licenses/>.

    This is the CPP parser.
*/

#ifndef CPP_PARSER_H
#define CPP_PARSER_H

#include <sstream>
using namespace std;

// The C-PreProcessor output parser
// The responsibility of this class
// is to parse the file as it comes.
// It has NO LOGIC regarding the handling
// of the line numbers. Use the listener below for that.
class CppParser
{
public:
    struct Listener
    {
        virtual void repositionFile(const string& fileName, size_t line) = 0;
        virtual void nonCPPLine() = 0;
        virtual void pushInclude(const string& newName, size_t line, bool systemInclude) = 0;
        virtual void popInclude(const string& oldName, size_t line) = 0;
        virtual ~Listener(){}
    };
private:
    Listener* const listener;

    enum LineType
    {
        IgnorableLine,
        RepositionFileLine,
        NonCPPLine,
        PushLine,
        PopLine
    };

    enum Flags
    {
        PushFlag = 1,
        PopFlag = 2,
        SystemFlag = 3,
        ExternCFlag = 4
    };

    void _parseLine(const string& line, LineType& type, size_t& lineNumber, string& fileName, bool& isSystem)
    {
        type = IgnorableLine;

        if (line.size() > 4 && line[0] == '#' && line[1] == ' ')
        {
            stringstream ss(line);
            char sharp;

            if (ss >> sharp >> lineNumber >> fileName)
            {
                if (sharp == '#' && fileName.size() > 2 && fileName[0] == '\"' && fileName[1] != '<')
                {
                    isSystem = false;

                    unsigned int flag1;
                    if (ss >> flag1)
                    {
                        if (flag1 == PushFlag)
                            type = PushLine;
                        else if (flag1 == PopFlag)
                            type = PopLine;

                        unsigned int flag2;
                        if (ss >> flag2)
                            isSystem = (flag2 == SystemFlag);
                    }
                    else
                        type = RepositionFileLine;
                }
            }
        }
        else
            type = NonCPPLine;
    }
public:
    CppParser(Listener* listener) : listener(listener){}

    void parseLine(const string& line)
    {
        LineType type;
        size_t lineNumber;
        string fileName;
        bool isSystem;

        _parseLine(line, type, lineNumber, fileName, isSystem);
        switch(type)
        {
            case RepositionFileLine:
                listener->repositionFile(fileName, lineNumber);
                break;

            case NonCPPLine:
                listener->nonCPPLine();
                break;

            case PushLine:
                listener->pushInclude(fileName, lineNumber, isSystem);
                break;

            case PopLine:
                listener->popInclude(fileName, lineNumber);

            case IgnorableLine: ;
        }
    }
};

// This is the listener that contains the logic of files
// and line numbers. It requires the user's listener.
class CppListenerFixer : public CppParser::Listener
{
public:
    struct Listener
    {
        virtual void setRoot(const string& rootName, size_t line) = 0;
        virtual void pushInclude(const string& newName, size_t line, bool systemInclude) = 0;
        virtual void popInclude(const string& oldName, size_t line) = 0;
        virtual ~Listener(){}
    };
private:
    Listener* const listener;
    size_t lastLine;
    string lastFile;
    bool setRootPending;
    bool lastWasCPPLine;

    virtual void repositionFile(const string& fileName, size_t line)
    {
        lastFile = fileName;
        lastLine = line;
        lastWasCPPLine = true;
    }

    virtual void nonCPPLine()
    {
        //if (!lastWasCPPLine)
            lastLine++;

        lastWasCPPLine = false;
    }

    virtual void pushInclude(const string& newName, size_t line, bool systemInclude)
    {
        if (setRootPending)
        {
            listener->setRoot(lastFile, line);
            setRootPending = false;
        }

        listener->pushInclude(newName, lastLine, systemInclude);
        lastLine = line;
        lastFile = newName;
        lastWasCPPLine = true;
    }

    virtual void popInclude(const string& oldName, size_t line)
    {
        listener->popInclude(oldName, line);
        lastLine = line;
        lastFile = oldName;
        lastWasCPPLine = true;
    }
public:
    CppListenerFixer(Listener* listener) : listener(listener), lastLine(1), setRootPending(true), lastWasCPPLine(false){}
};


#endif
